<?php
namespace App\Services\Tool\Svg;
use Log, Cache;
/**
 * EasySVG - Generate SVG from PHP
 * @author Simon Tarchichi <kartsims@gmail.com>
 * @version 0.1b
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
 * @see http://stackoverflow.com/questions/14684846/flattening-svg-matrix-transforms-in-inkscape
 * @see http://stackoverflow.com/questions/7742148/how-to-convert-text-to-svg-paths
 */
class SVGFont {
    protected $font;
    protected $svg;
    private   $font_family = [];
    private   $font_size = [];
    private   $newline = [];
    private   $fill = [];
    private   $unit_height = 0;
    private   $unit_width =0;
    private   $ascent = 0;
    private   $descent = 0;
    private   $unitsPerEm = 0;
    private   $text_width = 0;
    private   $text_height = 0;
    private   $scale = 1;
    private   $x_begin = 0;
    private   $font_now = '';
    protected $header = '<?xml version="1.0" encoding="UTF-8" standalone="no"?>';
    public function __construct() {
        // default font data
        $this->font = new stdClass;
        $this->font->id = '';
        $this->font->horizAdvX = 0;
        $this->font->unitsPerEm = 0;
        $this->font->ascent = 0;
        $this->font->descent = 0;
        $this->font->glyphs = array();
        $this->font->size = 100;
        $this->font->color = '#000000';
        $this->font->lineHeight = 1;
        $this->font->letterSpacing = 0;
        $this->clearSVG();
    }
    public function clearSVG() {
        $this->svg = new \SimpleXMLElement('<svg></svg>');
    }

    public function set_font_fill(array $arr){
        $this->fill = $arr;
    }

    public function set_font_newline($arr){
        $this->newline = $arr;
    }

    public function set_text_width($arr){
        $this->text_width = $arr;
    }

    public function set_text_height($arr){
        $this->text_height = $arr;
    }

    public function get_text_width(){
        return $this->text_width;
    }

    public function get_text_height(){
        return $this->text_height;
    }

    public function set_font_size(array $arr){
        $this->font_size = $arr;
    }

    public function set_font_family(array $arr){
        $this->font_family = $arr;
    }
    public function set_scale($num){
        $this->scale = $num;
    }
    public function get_x_begin(){
        return $this->x_begin;
    }
    public function set_x_begin($num){
        $this->x_begin = $num;
    }
    /**
     * Function takes UTF-8 encoded string and returns unicode number for every character.
     * @param  string $str
     * @return string
     */
    private function _utf8ToUnicode( $str ) {
        $unicode = array();
        $values = array();
        $lookingFor = 1;
        for ($i = 0; $i < strlen( $str ); $i++ ) {
            $thisValue = ord( $str[ $i ] );
            if ( $thisValue < 128 ) $unicode[] = $thisValue;
            else {
                if ( count( $values ) == 0 ) $lookingFor = ( $thisValue < 224 ) ? 2 : 3;
                $values[] = $thisValue;
                if ( count( $values ) == $lookingFor ) {
                    $number = ( $lookingFor == 3 ) ?
                        ( ( $values[0] % 16 ) * 4096 ) + ( ( $values[1] % 64 ) * 64 ) + ( $values[2] % 64 ):
                        ( ( $values[0] % 32 ) * 64 ) + ( $values[1] % 64 );
                    $unicode[] = $number;
                    $values = array();
                    $lookingFor = 1;
                }
            }
        }
        return $unicode;
    }
    /**
     * Set font params (short-hand method)
     * @param string $filepath
     * @param integer $size
     * @param string $color
     */
    public function setFont( $filepath, $size, $color ) {
        $this->setFontSVG($filepath);
        $this->setFontSize($size);
        $this->setFontColor($color);
    }
    /**
     * Set font size for display
     * @param int $size
     * @return void
     */
    public function setFontSize( $size ) {
        $this->font->size = $size;
    }
    /**
     * Set font color
     * @param string $color
     * @return void
     */
    public function setFontColor( $color ) {
        $this->font->color = $color;
    }
    /**
     * Set the line height from default (1) to custom value
     * @param  float $value
     * @return void
     */
    public function setLineHeight( $value ) {
        $this->font->lineHeight = $value;
    }
    /**
     * Set the letter spacing from default (0) to custom value
     * @param  float $value
     * @return void
     */
    public function setLetterSpacing( $value ) {
        $this->font->letterSpacing = $value;
    }
    /**
     * Function takes path to SVG font (local path) and processes its xml
     * to get path representation of every character and additional
     * font parameters
     * @param  string $filepath
     * @return void
     */
    public function setFontSVG( $filepath ) {
        if(Cache::has($filepath)){
            $this->font = Cache::get($filepath);
            return ;
        }
        $this->font->glyphs = array();
        $z = new \XMLReader;
        $z->open($filepath);
        // move to the first <product /> node
        while ($z->read()) {
            $name = $z->name;
            if ($z->nodeType == \XMLReader::ELEMENT) {
                if ($name == 'font') {
                    $this->font->id = $z->getAttribute('id');
                    $this->font->horizAdvX = $z->getAttribute('horiz-adv-x');
                }
                if ($name == 'font-face') {
                    $this->font->unitsPerEm = $z->getAttribute('units-per-em');
                    $this->font->ascent = $z->getAttribute('ascent');
                    $this->font->descent = $z->getAttribute('descent');
                }
                if ($name == 'glyph') {
                    $unicode = $z->getAttribute('unicode');
                    $unicode = $this->_utf8ToUnicode($unicode);

                    if (isset($unicode[0])) {
                        $unicode = $unicode[0];

                        $this->font->glyphs[$unicode] = new stdClass();
                        $this->font->glyphs[$unicode]->horizAdvX = $z->getAttribute('horiz-adv-x');
                        if (empty($this->font->glyphs[$unicode]->horizAdvX)) {
                            $this->font->glyphs[$unicode]->horizAdvX = $this->font->horizAdvX;
                        }
                        $this->font->glyphs[$unicode]->d = $z->getAttribute('d');

                        // save em value for letter spacing (109 is unicode for the letter 'm')
                        if ($unicode == '109') {
                            $this->font->em = $this->font->glyphs[$unicode]->horizAdvX;
                        }
                    }
                }
            }
        }
        Cache::forever($filepath,$this->font);
    }
    /**
     * Add a path to the SVG
     * @param string $def
     * @param array $attributes
     * @return SimpleXMLElement
     */
    public function addPath($def, $attributes=array()) {
        $path = $this->svg->addChild('path');

        foreach($attributes as $key=>$value){
            $path->addAttribute($key, $value);
        }
        $path->addAttribute('d', $def);

        return $path;
    }
    /**
     * Add a text to the SVG
     * @param string $def
     * @param float $x
     * @param float $y
     * @param array $attributes
     * @return SimpleXMLElement
     */
    public function addText($text, $x=0, $y=0, $attributes=array()) {

        $def = $this->textDef($text);
        foreach($def as $k=>$d){
            if($x!=0 || $y!=0){
                $def = $this->defTranslate($d, $x, $y);
            }
            $attributes['fill'] = $this->fill[$k];
            $this->addPath($d, $attributes);
        }

        // return $this->addPath($def, $attributes);
        return true;
    }

    /**
     * Function takes max lineheight from font_family
     */
    public function get_font_line_height(){
        $lineHeight = 0;
        $array = [];

        foreach($this->font_family as $num=>$one){
            if(in_array($one,$array)) continue;
            $this->setFontSVG(storage_path('font' . DIRECTORY_SEPARATOR . $one .'.svg'));
            if($this->font_now != $one) $this->font_now = $one;
            $fontSize = $this->font->unitsPerEm != 0 ? floatval($this->font_size[$num]) / $this->font->unitsPerEm : floatval($this->font_size[$num]);
            $now = ($this->font->ascent + $this->font->descent) * $fontSize * 2;
            //var_dump($now);
            // $now = $this->font->ascent + $this->font->descent;


            if($now > $lineHeight){
                $lineHeight       = $now;
                $this->ascent     = $this->font->ascent;
                $this->descent    = $this->font->descent;
                $this->unitsPerEm = $this->font->unitsPerEm;
            }
            $array[] = $one;
        }
        return $lineHeight;
    }

    /**
     * Function takes UTF-8 encoded string and size, returns xml for SVG paths representing this string.
     * 以UTF-8编码的字符串大小，返回在SVG中的XML格式的paths(描点点坐标)
     *
     * @param string $text UTF-8 encoded text
     * @return string xml for text converted into SVG paths
     */
    public function textDef($text) {
        $def = array();
        $horizAdvX = 0;
        $horizAdvY = 0;


        $lineHeight = $this->get_font_line_height();

        $font_now = $this->font_now;
        // $horizAdvY = $this->font->ascent + $this->font->descent;
        // $fontSize = $this->font->unitsPerEm != 0 ? floatval($this->font->size) / $this->font->unitsPerEm : floatval($this->font->size);
        $text = $this->_utf8ToUnicode($text);
        $log = [];
        for($i = 0; $i < count($text); $i++) {
            // if(isset($log[$text[$i] . '_' . $this->font_family[$i] . '_' . $this->font_size[$i]])){
            //     $def[] = $log[$text[$i] . '_' . $this->font_family[$i] . '_' . $this->font_size[$i]];
            //     continue;
            // }
            $letter = $text[$i];
            if($font_now != $this->font_family[$i]){
                $this->setFontSVG(storage_path('font' . DIRECTORY_SEPARATOR . $this->font_family[$i] .'.svg'));
                $font_now = $this->font_family[$i];
            }

            $fontSize = $this->unitsPerEm != 0 ? floatval($this->font_size[$i]) / $this->unitsPerEm : floatval($this->font_size[$i]);

            //if($this->ascent + $this->descent > $lineHeight) $lineHeight = $this->ascent + $this->descent;

            // if($this->ascent + $this->descent > $lineHeight) $lineHeight = ($this->ascent + $this->descent) * $fontSize * 2;
            $this->setFontSize($this->font_size[$i]);
            if($this->newline[$i]){
                $horizAdvY += $lineHeight * $this->get_ratio_from_font($this->font_family);
                // $horizAdvY += $lineHeight/5*4;
                $horizAdvX = $this->get_horizAdvX($this->font_family[$i],($this->font_size[$i]/123.23943661972));
            }
            // Log::info('textDimensions_start:' . microtime());//169
            $this->textDimensions($text[$i],$this->newline[$i]);
            // Log::info('textDimensions_end:' . microtime());//74



            // var_dump($this->font_family[$i]);
            // line break support (10 is unicode for linebreak)
            if($letter==10){
                $horizAdvX = 0;
                $horizAdvY += $this->font->lineHeight * ( $this->ascent + $this->descent );
                continue;
            }
 //           if(Cache::store('file')->has($letter . '_' . $this->font_family[$i] . '_' . $fontSize)){
                //147
//                $d = Cache::store('file')->get($letter . '_' . $this->font_family[$i] . '_' . $fontSize);

//            }else{
                // extract character definition
                $d = $this->font->glyphs[$letter]->d;
                // Log::info('defScale_start:' . microtime());//61
                // transform typo from original SVG format to straight display
                $d = $this->defScale($d, $fontSize, -$fontSize);
                // Log::info('defScale_end:' . microtime());//375
//                Cache::store('file')->forever($letter . '_' . $this->font_family[$i] . '_' . $fontSize,$d);
 //           }

            $d = $this->defTranslate($d, $horizAdvX, $horizAdvY);
            //$d = $this->defTranslate($d, $horizAdvX, $horizAdvY*$fontSize*2);var_dump($horizAdvY*$fontSize*2);
            // Log::info('defTranslate_end:' . microtime());//361
            $def[] = $d;
            // $log[$text[$i] . '_' . $this->font_family[$i] . '_' . $this->font_size[$i]] = $d;
            // next letter's position
            $horizAdvX += $this->font->glyphs[$letter]->horizAdvX * $fontSize + $this->font->em * $this->font->letterSpacing * $fontSize;
            $this->set_text_width($horizAdvX > $this->get_text_width() ? $horizAdvX : $this->get_text_width());
        }

        // dd($def);
        return $def;
    }

    private function horizAdvY($horizAdvY,$fontSize){
        if($this->scale > 5 && $this->scale <= 8){
            return $horizAdvY;
        }else if($this->scale > 8 && $this->scale <= 9.5){
            return $horizAdvY*$fontSize*1.2;
        }else if($this->scale > 9.5 && $this->scale <= 10.5){
            return $horizAdvY*$fontSize*1.1;
        }else if($this->scale > 10.5 && $this->scale <= 11.5){
            return $horizAdvY*$fontSize*0.8;
        }else{
            return $horizAdvY*$fontSize*2;
        }
    }

    /**
     * Function takes UTF-8 encoded string and size, returns width and height of the whole text
     * @param string $text UTF-8 encoded text
     * @return array ($width, $height)
     */
    public function textDimensions($text,$new_line) {
        $def = array();
        $fontSize = $this->unitsPerEm != 0 ? floatval($this->font->size) / $this->unitsPerEm : floatval($this->font->size);
        // $text = $this->_utf8ToUnicode($text);

        $lineHeight = ( $this->ascent + $this->descent ) * $fontSize * 2;// * $this->scale;
        // $lineHeight = ( $this->ascent + $this->descent ) * $fontSize * 2;

        $height = $lineHeight;
        // for($i = 0; $i < count($text); $i++) {
        //     $letter = $text[$i];
            // line break support (10 is unicode for linebreak)
            if($text==10){

                $height += $lineHeight * $this->font->lineHeight;

                // continue;
            }
        // }
        // only keep the widest line's width
        //var_dump('height' . $this->unit_height);
        $this->unit_height = $this->unit_height == 0 ? $height : ($this->unit_height > $height ? $this->unit_height : $height);

        //$this->set_text_width(($this->unit_width > $this->get_text_width() ? $this->unit_width : $this->get_text_width()) + $more);

        $this->set_text_height($this->unit_height * count(array_keys($this->newline,1)));

        return array($height);
    }
    /**
     * Function takes unicode character and returns the UTF-8 equivalent
     * @param  string $str
     * @return string
     */
    public function unicodeDef( $unicode ) {
        $horizAdvY = $this->font->ascent + $this->font->descent;
        $fontSize =  floatval($this->font->size) / $this->font->unitsPerEm;
        // extract character definition
        $d = $this->font->glyphs[hexdec($unicode)]->d;
        // transform typo from original SVG format to straight display
        $d = $this->defScale($d, $fontSize, -$fontSize);
        $d = $this->defTranslate($d, 0, $horizAdvY*$fontSize*2);
        return $d;
    }
    /**
     * Returns the character width, as set in the font file
     * @param  string  $str
     * @param  boolean $is_unicode
     * @return float
     */
    public function characterWidth( $char, $is_unicode = false ) {
        if ($is_unicode){
            $letter = hexdec($char);
        }
        else {
            $letter = $this->_utf8ToUnicode($char);
        }
        if (!isset($this->font->glyphs[$letter]))
            return NULL;
        $fontSize = floatval($this->font->size) / $this->font->unitsPerEm;
        return $this->font->glyphs[$letter]->horizAdvX * $fontSize;
    }

    /**
     * Applies a translate transformation to definition
     * @param  string  $def definition
     * @param  float $x
     * @param  float $y
     * @return string
     */
    public function defTranslate($def, $x=0, $y=0){
        return $this->defApplyMatrix($def, array(1, 0, 0, 1, $x, $y));
    }

    /**
     * Applies a translate transformation to definition
     * @param  string  $def    Definition
     * @param  integer $angle  Rotation angle (degrees)
     * @param  integer $x      X coordinate of rotation center
     * @param  integer $y      Y coordinate of rotation center
     * @return string
     */
    public function defRotate($def, $angle, $x=0, $y=0){
        if($x==0 && $y==0){
            $angle = deg2rad($angle);
            return $this->defApplyMatrix($def, array(cos($angle), sin($angle), -sin($angle), cos($angle), 0, 0));
        }
        // rotate by a given point
        $def = $this->defTranslate($def, $x, $y);
        $def = $this->defRotate($def, $angle);
        $def = $this->defTranslate($def, -$x, -$y);
        return $def;
    }

    /**
     * Applies a scale transformation to definition
     * @param  string  $def definition
     * @param  integer $x
     * @param  integer $y
     * @return string
     */
    public function defScale($def, $x=1, $y=1){
        return $this->defApplyMatrix($def, array($x, 0, 0, $y, 0, 0));
    }
    /**
     * Calculates the new definition with the matrix applied
     * @param  string $def
     * @param  array  $matrix
     * @return string
     */
    public function defApplyMatrix($def, $matrix){
        // if there are several shapes in this definition, do the operation for each
        preg_match_all('/M[^zZ]*[zZ]/', $def, $shapes);
        $shapes = $shapes[0];
        if(count($shapes)>1){
            foreach($shapes as &$shape)
                $shape = $this->defApplyMatrix($shape, $matrix);
            return implode(' ', $shapes);
        }
        preg_match_all('/[a-zA-Z]+[^a-zA-Z]*/', $def, $instructions);
        $instructions = $instructions[0];
        $return = '';
        foreach($instructions as &$instruction){
            $i = preg_replace('/[^a-zA-Z]*/', '', $instruction);
            preg_match_all('/\-?[0-9\.]+/', $instruction, $coords);
            $coords = $coords[0];
            if(empty($coords)){
                continue;
            }

            $new_coords = array();
            while(count($coords)>0){
                // do the matrix calculation stuff
                list($a, $b, $c, $d, $e, $f) = $matrix;
                // exception for relative instruction
                if( preg_match('/[a-z]/', $i) ){
                    $e = 0;
                    $f = 0;
                }
                // convert horizontal lineto (relative)
                if( $i=='h' ){
                    $i = 'l';
                    $x = floatval( array_shift($coords) );
                    $y = 0;
                    // add new point's coordinates
                    $current_point = array(
                        $a*$x + $c*$y + $e,
                        $b*$x + $d*$y + $f,
                    );
                    $new_coords = array_merge($new_coords, $current_point);
                }
                // convert vertical lineto (relative)
                elseif( $i=='v' ){
                    $i = 'l';
                    $x = 0;
                    $y = floatval( array_shift($coords) );
                    // add new point's coordinates
                    $current_point = array(
                        $a*$x + $c*$y + $e,
                        $b*$x + $d*$y + $f,
                    );
                    $new_coords = array_merge($new_coords, $current_point);
                }
                // convert quadratic bezier curve (relative)
                elseif( $i=='q' ){
                    $x = floatval( array_shift($coords) );
                    $y = floatval( array_shift($coords) );
                    // add new point's coordinates
                    $current_point = array(
                        $a*$x + $c*$y + $e,
                        $b*$x + $d*$y + $f,
                    );
                    $new_coords = array_merge($new_coords, $current_point);
                    // same for 2nd point
                    $x = floatval( array_shift($coords) );
                    $y = floatval( array_shift($coords) );
                    // add new point's coordinates
                    $current_point = array(
                        $a*$x + $c*$y + $e,
                        $b*$x + $d*$y + $f,
                    );
                    $new_coords = array_merge($new_coords, $current_point);
                }
                // every other commands
                // @TODO: handle 'a,c,s' (elliptic arc curve) commands
                // cf. http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands
                else{
                    $x = floatval( array_shift($coords) );
                    $y = floatval( array_shift($coords) );

                    // add new point's coordinates
                    $current_point = array(
                        $a*$x + $c*$y + $e,
                        $b*$x + $d*$y + $f,
                    );
                    $new_coords = array_merge($new_coords, $current_point);
                }
            }
            $instruction = $i . implode(',', $new_coords);
            // remove useless commas
            $instruction = preg_replace('/,\-/','-', $instruction);
        }
        return implode('', $instructions);
    }
    /**
     *
     * Short-hand methods
     *
     */

    /**
     * Return full SVG XML
     * @return string
     */
    public function asXML($file_path){
        return $file_path != '' ? $this->svg->asXML($file_path) : $this->svg->asXML();
    }
    /**
     * Adds an attribute to the SVG
     * @param string $key
     * @param string $value
     */
    public function addAttribute($key, $value){
        return $this->svg->addAttribute($key, $value);
    }

    public function asSvg(){
        $arr = (array)$this->svg;
        $arr['path'] = (array)$arr['path'];//dd($arr['@attributes']);
        $str = $this->header;
        $str .= '<svg width="' . $arr['@attributes']['width'] .
                '" height="' . $arr['@attributes']['height'] .
                '" >';
        $str .= '<g> ';
        $str .= '<path transform="translate(0,0) rotate(180) scale(-1, 1)" fill="' . $arr['path']['@attributes']['fill'] . '" d="' .
                $arr['path']['@attributes']['d'] . '"></path></g></svg>';
        return $str;
    }

    public function get_array_to_use($param, $scale = 1){
        Log::info($scale);
        $param              = (array)$param;
        $arr['font_family'] = [];
        $arr['font_size']   = [];
        $arr['newline']     = [];
        $arr['text']        = '';
        $arr['scale']       = $scale;
        if(!isset($param['text_svg_info'])) Throw new \Exception('参数不存在');
        foreach($param['text_svg_info'] as $text){
            $text = (array)$text;
            $arr['font_family'][] = $text['font_family'];
            $arr['font_size'][]   = $text['font_size'] * $scale;
            $arr['fill'][]        = isset($text['fill']) ? $text['fill'] : '#000000';
            $arr['text']          .= $text['char'];
            $arr['newline'][]     = isset($text['newline']) ? $text['newline'] : 0;
        }
        return $arr;
    }

    function check_str($str){
        $types = 0;
        for($i=0;$i<strlen($str);$i++){
            if(ord(substr($str,$i,1))>0xa0){
                $types=1;
            }
        }
        return $types;
    }

    function replace_str_from_full_width($str){
        $arr = array('０' => '0', '１' => '1', '２' => '2', '３' => '3', '４' => '4','５' => '5', '６' => '6', '７' => '7', '８' => '8', '９' => '9',
                     'Ａ' => 'A', 'Ｂ' => 'B', 'Ｃ' => 'C', 'Ｄ' => 'D', 'Ｅ' => 'E','Ｆ' => 'F', 'Ｇ' => 'G', 'Ｈ' => 'H', 'Ｉ' => 'I', 'Ｊ' => 'J',
                     'Ｋ' => 'K', 'Ｌ' => 'L', 'Ｍ' => 'M', 'Ｎ' => 'N', 'Ｏ' => 'O','Ｐ' => 'P', 'Ｑ' => 'Q', 'Ｒ' => 'R', 'Ｓ' => 'S', 'Ｔ' => 'T',
                     'Ｕ' => 'U', 'Ｖ' => 'V', 'Ｗ' => 'W', 'Ｘ' => 'X', 'Ｙ' => 'Y','Ｚ' => 'Z',
                     'ａ' => 'a', 'ｂ' => 'b', 'ｃ' => 'c', 'ｄ' => 'd','ｅ' => 'e', 'ｆ' => 'f', 'ｇ' => 'g', 'ｈ' => 'h', 'ｉ' => 'i','ｊ' => 'j',
                     'ｋ' => 'k', 'ｌ' => 'l', 'ｍ' => 'm', 'ｎ' => 'n','ｏ' => 'o', 'ｐ' => 'p', 'ｑ' => 'q', 'ｒ' => 'r', 'ｓ' => 's', 'ｔ' => 't',
                     'ｕ' => 'u', 'ｖ' => 'v', 'ｗ' => 'w', 'ｘ' => 'x', 'ｙ' => 'y', 'ｚ' => 'z',
                     '（' => '(', '）' => ')', '〔' => '[', '〕' => ']', '【' => '[','】' => ']', '〖' => '[', '〗' => ']', '“' => '[', '”' => ']',
                     '｛' => '{', '｝' => '}', '《' => '<','》' => '>','％' => '%', '＋' => '+', '—' => '-', '－' => '-', '！' => '!', '…' => '-',
                     '”' => '"','‘' => '`', '｜' => '|', '〃' => '"','　' => ' ');
        return isset($arr[$str]) ? $arr[$str] : $str;
    }
    function get_horizAdvX($file_name,$li){
        $re = 0;
        switch($file_name){
            case '2291572f436153215002f01e1fc800d0':
                $re = 25 * $li;
                break;
            case 'SourceHanSansCN-Normal':
                $re = 3 * $li;
                break;
            default:
                break;
        }
        $this->set_x_begin($re > $this->x_begin ? $re : $this->x_begin);
        return $re;
    }
    function get_ratio_from_font($font_name){

            if(in_array('2291572f436153215002f01e1fc800d0',$font_name)){
                return 0.69;
            }else{
                return 0.75;
            }
        }
}
class stdClass{}
